using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using Deepcode.Flux.Core.Data;

namespace Deepcode.Flux.Core.Systems.Registry
{
	/// <summary>
	/// RegistryManager class provides the manager interface for the Flux registry database tables.
	/// </summary>
	public class RegistryDataManager : BaseManager
	{
		#region Constructors
		public RegistryDataManager() : base() { }
		public RegistryDataManager(SqlConnection CurrentConnection) : base(CurrentConnection) { }
		public RegistryDataManager(SqlConnection CurrentConnection, SqlTransaction CurrentTransaction) : base(CurrentConnection, CurrentTransaction) { }
		#endregion
		#region Get Node Record function - a large and key function for this class
		/// <summary>
		/// Gets a record from the registry. It searches by the path specified and retrieves the item with the
		/// specified key. Optionally the path will be created if Create is specified to true.
		/// </summary>
		/// <param name="Path"></param>
		/// <param name="Key"></param>
		/// <param name="Create"></param>
		/// <returns></returns>
		public RegistryDataNode GetNodeRecord(string Path, string Key, bool Create)
		{
			string[] paths = Path.Split('/');
			int ParentID = -1;
			try
			{
				// Ensure we have a connection and a transaction
				EstablishConnection();
				BeginTransaction();

				// First, enumerate and find all of the path records
				foreach (string path in paths)
				{
					// Ignore empty paths
					if (path == "") continue;
					// Find the path within the current parent ID
					RegistryDataNode foundNode = GetByParentAndName(path, ParentID);
					// Determine what to do next
					if (foundNode == null)
					{
						// The node path was not found
						if (Create)
						{
							// Create the new node path and set foundnode
							RegistryDataNode createNode = AddNewNode(ParentID, path, FluxRegistryKeyType.NODEPATH, "");
							if (createNode == null) throw new FluxRegistryNonFatalException();
							foundNode = createNode;
						}
						else
						{
							// We're not creating new nodes and it wasn't found, cannot continue...
							throw new FluxRegistryNonFatalException();
						}
					}
					// Set the next search parentID to the found node.
					ParentID = foundNode.PK_ID;
				}

				// Now, find the key node
				RegistryDataNode KeyNode = GetByParentAndName(Key, ParentID);
				if (KeyNode == null)
				{
					if (Create)
					{
						KeyNode = AddNewNode(ParentID, Key, FluxRegistryKeyType.NODEPATH, "");
						if (KeyNode == null) throw new FluxRegistryNonFatalException();
					}
					else
					{
						throw new FluxRegistryNonFatalException();
					}
				}
				// Commit anything outstanding on the transaction
				EndTransaction(EndTransactionMode.COMMIT);
				ReleaseConnection();
				// Now, we have the Key Node, return it
				return KeyNode;
			}
			catch (FluxRegistryNonFatalException)
			{
				// This exception just indicates we need to return null
				EndTransaction(EndTransactionMode.ROLLBACK);
				ReleaseConnection();
				return null;
			}
			catch (Exception e)
			{
				// Other exceptions - clean up and re-throw
				EndTransaction(EndTransactionMode.ROLLBACK);
				ReleaseConnection();
				throw e;
			}
		}
		#endregion
		#region Node finders
		/// <summary>
		/// Returns a registry node by it's ID
		/// </summary>
		/// <param name="NodeID"></param>
		/// <returns></returns>
		public RegistryDataNode GetByID(int NodeID)
		{
			DataTable results = new DataTable();
			
			EstablishConnection();
			SqlDataAdapter da = this.CreateDataAdapter("coreRegistryGetByID");
			da.SelectCommand.Parameters.Add( SQLUtility.ParameterIn("@id", SqlDbType.Int, 4, NodeID));
			da.Fill( results );
			ReleaseConnection();

			if( results == null ) return null;
			if( results.Rows.Count < 1 ) return null;
			return new RegistryDataNode(results.Rows[0]);
		}
		/// <summary>
		/// Finds the record with the given key within the parent node specified by it's ID
		/// </summary>
		/// <param name="KeyName"></param>
		/// <param name="ParentID"></param>
		/// <returns></returns>
		public RegistryDataNode GetByParentAndName(string KeyName, int ParentID)
		{
			DataTable results = new DataTable();

			EstablishConnection();
			SqlDataAdapter da = this.CreateDataAdapter("coreRegistryGetByParentAndName");
			da.SelectCommand.Parameters.Add(SQLUtility.ParameterIn("@pid", SqlDbType.Int, 4, ParentID));
			da.SelectCommand.Parameters.Add(SQLUtility.ParameterIn("@keyname", SqlDbType.NVarChar, 100, KeyName));
			da.Fill(results);
			ReleaseConnection();

			if (results == null) return null;
			if (results.Rows.Count < 1) return null;
			return new RegistryDataNode(results.Rows[0]);
		}
		/// <summary>
		/// Returns an array of nodes representing the children of the node ID specified.
		/// </summary>
		/// <param name="ParentID"></param>
		/// <returns></returns>
		public RegistryDataNode[] GetChildrenFor(int ParentID)
		{
			List<RegistryDataNode> l = new List<RegistryDataNode>();

			DataTable results = new DataTable();
			EstablishConnection();
			SqlDataAdapter da = this.CreateDataAdapter("coreRegistryGetChildrenFor");
			da.SelectCommand.Parameters.Add(SQLUtility.ParameterIn("@pid", SqlDbType.Int, 4, ParentID));
			da.Fill(results);
			ReleaseConnection();

			foreach (DataRow r in results.Rows)
			{
				RegistryDataNode n = new RegistryDataNode(r);
				if (n != null) l.Add(n);
			}

			return l.ToArray();
		}
		#endregion
		#region Node creations and saver
		/// <summary>
		/// Adds a new node to the registry
		/// </summary>
		/// <param name="ParentID"></param>
		/// <param name="KeyName"></param>
		/// <param name="NodeType"></param>
		/// <param name="Value"></param>
		/// <returns></returns>
		public RegistryDataNode AddNewNode(int ParentID, string KeyName, FluxRegistryKeyType NodeType, string Value)
		{
			RegistryDataNode node = new RegistryDataNode();
			node.FK_ParentID = ParentID;
			node.KeyName = KeyName;
			node.KeyType = (int)NodeType;
			node.ValueString = Value;
			Save(node);
			return node;
		}
		/// <summary>
		/// Saves the specified node to the database
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public int Save(RegistryDataNode node)
		{
			EstablishConnection();
			SqlCommand cmd = this.CreateSqlCommand("coreRegistrySave");
			SqlParameter result = SQLUtility.ParameterInOut("@ID", SqlDbType.Int, 4, node.PK_ID);

			cmd.Parameters.Add(result);
			cmd.Parameters.Add(SQLUtility.ParameterIn("@FK_ParentID", SqlDbType.Int, 4, node.FK_ParentID));
			cmd.Parameters.Add(SQLUtility.ParameterIn("@KeyName", SqlDbType.NVarChar, 100, node.KeyName));
			cmd.Parameters.Add(SQLUtility.ParameterIn("@KeyType", SqlDbType.Int, 4, node.KeyType));
			cmd.Parameters.Add(SQLUtility.ParameterIn("@ValueString", SqlDbType.NVarChar, 500, node.ValueString));

			cmd.ExecuteNonQuery();
			ReleaseConnection();

			if (node.PK_ID == -1)
				node.PK_ID = (int)result.Value;

			return node.PK_ID;
		}
		#endregion
		#region Node Value Getters/Setters
		/// <summary>
		/// Sets the node's type,key and value
		/// </summary>
		/// <param name="node"></param>
		/// <param name="val"></param>
		/// <param name="nodetype"></param>
		protected void SetKeyValue(RegistryDataNode node, string val, FluxRegistryKeyType nodetype)
		{
			if (node == null) return;
			node.KeyType = (int) nodetype;
			node.ValueString = val;
			Save(node);
		}
		/// <summary>
		/// Sets the node's value to the value specified. 
		/// </summary>
		/// <param name="node"></param>
		/// <param name="val"></param>
		public void SetKeyValue(RegistryDataNode node, string val)
		{
			SetKeyValue( node, val, FluxRegistryKeyType.NODESTRING );
		}
		public void SetKeyValue(RegistryDataNode node, int val)
		{
			SetKeyValue(node, val.ToString(), FluxRegistryKeyType.NODEINT);
		}
		public void SetKeyValue(RegistryDataNode node, bool val)
		{
			SetKeyValue(node, val.ToString(), FluxRegistryKeyType.NODEBOOL);
		}
		public void SetKeyValue(RegistryDataNode node, DateTime val)
		{
			SetKeyValue(node, val.ToString(), FluxRegistryKeyType.NODESTRING);
		}
		public void SetKeyValue(RegistryDataNode node, Enum val)
		{
			SetKeyValue(node, val.ToString(), FluxRegistryKeyType.NODESTRING);
		}
		/// <summary>
		/// Gets the value of the node specified by basepath, keyname. If not found,
		/// returns default value instead.
		/// </summary>
		/// <param name="BasePath"></param>
		/// <param name="KeyName"></param>
		/// <param name="DefaultValue"></param>
		/// <returns></returns>
		public string GetValue(string BasePath, string KeyName, string DefaultValue)
		{
			RegistryDataNode node = GetNodeRecord(BasePath, KeyName, false);
			if (node == null) return DefaultValue;
			return node.ValueString;
		}
		public int GetValue(string BasePath, string KeyName, int DefaultValue)
		{
			string v = GetValue(BasePath, KeyName, (string)null);
			if (v == null) return DefaultValue;
			try { return Int32.Parse(v); }
			catch { return DefaultValue; }
		}
		public DateTime GetValue(string BasePath, string KeyName, DateTime DefaultValue)
		{
			string v = GetValue(BasePath, KeyName, (string)null);
			if (v == null) return DefaultValue;
			try { return DateTime.Parse(v); }
			catch { return DefaultValue; }
		}
		public bool GetValue(string BasePath, string KeyName, bool DefaultValue)
		{
			string v = GetValue(BasePath, KeyName, (string)null);
			if (v == null) return DefaultValue;
			try { return bool.Parse(v); }
			catch { return DefaultValue; }
		}
		public Enum GetValue(string BasePath, string KeyName, Enum DefaultValue)
		{
			string v = GetValue(BasePath, KeyName, (string)null);
			if (v == null) return DefaultValue;
			try { return (Enum)Enum.Parse(DefaultValue.GetType(), v, true); }
			catch { return DefaultValue; }
		}
		#endregion
		#region Node Deletions
		/// <summary>
		/// Removes the node specified and any recursive children...
		/// </summary>
		/// <param name="PK_ID"></param>
		public void DeleteByID(int PK_ID)
		{
			try
			{
				EstablishConnection();
				BeginTransaction();

				RegistryDataNode[] children = GetChildrenFor(PK_ID);
				foreach (RegistryDataNode child in children)
					DeleteByID(child.PK_ID);

				SqlCommand cmd = this.CreateSqlCommand("coreRegistryDeleteByID");
				cmd.Parameters.Add(SQLUtility.ParameterIn("@id", SqlDbType.Int, 4, PK_ID));
				cmd.ExecuteNonQuery();

				EndTransaction(EndTransactionMode.COMMIT);
				ReleaseConnection();
			}
			catch (Exception e)
			{
				try { EndTransaction(EndTransactionMode.ROLLBACK); }
				catch { }
				ReleaseConnection();
				throw e;
			}
		}
		#endregion
	}
}
